/* Copyright (C) 2006, 2007 by Daniel Gheorghian
 All rights reserved

 Use and distribute freely, except: don't remove my name from the source or documentation
 (don't take credit for my work), mark your changes (don't get me blamed for your possible bugs),
 don't alter or remove this notice.

 No warrantee of any kind, express or implied, is included with this software; use at your
 own risk, responsibility for damages (if any) to anyone resulting from the use of this
 software rests entirely with the user.

 Send bug reports, bug fixes, enhancements, requests, flames, etc., and I'll try to keep
 a version up to date.  I can be reached at: daniel.gheorghian@gmail.com
*/
package com.ds.asm.parser;

import com.ds.asm.instruction.*;

import java.io.*;
import java.util.Map;
import java.util.HashMap;
import java.util.Hashtable;
import java.lang.reflect.Field;

public class ASMParser
{
	private Map<String, LABEL> labels = new Hashtable<String, LABEL>();

	private static final Map<String,Integer> instructions = new HashMap<String, Integer>();

	static
	{
		Instructions i = new Instructions()
		{
		};
		Field[] fields = Instructions.class.getFields();

		for( Field f : fields )
		{
			try
			{
				instructions.put( f.getName( ), f.getInt( i ) );
			}
			catch( IllegalAccessException ex )
			{
				ex.printStackTrace();
			}
		}
	}

	public Instruction parse( File f ) throws IOException
	{
		BufferedReader reader = new BufferedReader( new FileReader( f ) );
		String line;
		LABEL start;
		Instruction instr1, instr2;
		Map<Instruction,String> references = new Hashtable<Instruction, String>();

		start = (LABEL)parseLine( reader.readLine() );
		instr1 = start;

		while( (line = reader.readLine()) != null )
		{
			instr2 = parseLine( line );

			if( instr1 instanceof SingleInputOutputInstruction )
			{
				if( instr1 instanceof JMP )
				{
					String i = instr1.getInstruction();
					String label = i.substring( i.indexOf( " " ) ).trim() + ":";
					references.put( instr1, label );
				}
				else
				{
					((SingleInputOutputInstruction)instr1).setOutput( instr2 );
				}
			}
			else
			{
				if( instr1 instanceof SingleInputDoubleOutputInstruction )
				{
					((SingleInputDoubleOutputInstruction)instr1).setOutputElse( instr2 );
					String i = instr1.getInstruction();
					String label = i.substring( i.indexOf( " " ) ).trim() + ":";
					references.put( instr1, label );
				}
				else
				{
					if( instr1 instanceof MultiInputSingleOutputInstruction )
					{
						((MultiInputSingleOutputInstruction)instr1).setOutput( instr2 );
					}
				}
			}

			if( instr2 instanceof SingleInputOutputInstruction )
			{
				((SingleInputOutputInstruction)instr2).setInput( instr1 );
			}
			else
			{
				if( instr2 instanceof SingleInputDoubleOutputInstruction )
				{
					((SingleInputDoubleOutputInstruction)instr2).setInput( instr1 );
				}
				else
				{
					if( instr2 instanceof MultiInputSingleOutputInstruction )
					{
						((MultiInputSingleOutputInstruction)instr2).addInput( instr1 );
					}
				}
			}

			if( instr2 instanceof LABEL )
			{
				this.labels.put( instr2.getInstruction(), (LABEL)instr2 );
			}

			instr1 = instr2;
		}

		if( instr1 instanceof SingleInputOutputInstruction )
		{
			((SingleInputOutputInstruction)instr1).setOutput( null );
		}
		else
		{
			if( instr1 instanceof SingleInputDoubleOutputInstruction )
			{
				((SingleInputDoubleOutputInstruction)instr1).setOutputElse( null );
			}
			else
			{
				if( instr1 instanceof MultiInputSingleOutputInstruction )
				{
					((MultiInputSingleOutputInstruction)instr1).setOutput( null );
				}
			}
		}

		for( Instruction i : references.keySet() )
		{
			if( i instanceof JMP )
			{
				((JMP)i).setOutput( this.labels.get( references.get( i ) ) );
			}
			else
			{
				((SingleInputDoubleOutputInstruction)i).setOutputThen( this.labels.get( references.get( i ) ) );
			}
		}

		return start;
	}

	private Instruction parseLine( String line )
	{
		Instruction instr = null;
		line = line.trim();

		// the instruction is a label
		if( line.endsWith( ":" ) )
		{
			instr = new LABEL( line );
		}
		else
		{
			String start = line.substring( 0, line.indexOf( " " ) ).toUpperCase();
			switch( instructions.get( start ) )
			{
				case Instructions.MOV:
					instr = new MOV( line );
					break;
				case Instructions.SUB:
					instr = new SUB( line );
					break;
				case Instructions.ADD:
					instr = new ADD( line );
					break;
				case Instructions.CMP:
					instr = new CMP( line );
					break;
				case Instructions.JMP:
					instr = new JMP( line );
					break;
				case Instructions.JE:
					instr = new JE( line );
					break;
				case Instructions.JG:
					instr = new JG( line );
					break;
				case Instructions.JL:
					instr = new JL( line );
					break;
				case Instructions.JGE:
					instr = new JGE( line );
					break;
				case Instructions.JLE:
					instr = new JLE( line );
					break;
				case Instructions.JNE:
					instr = new JNE( line );
					break;
				case Instructions.JNL:
					instr = new JNL( line );
					break;
				case Instructions.JNG:
					instr = new JNG( line );
					break;
				case Instructions.JNLE:
					instr = new JNLE( line );
					break;
				case Instructions.JNGE:
					instr = new JNGE( line );
					break;
			}
		}

		return instr;
	}
}
